﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using BCCommon;
using BCCommon.Enums.Chats;
using BCCommon.Exceptions;
using BCCommon.Extensions;
using BCEntity.Chats;
using BCData.Chats;
using BCDto.Chats.Thread;
using BCDto.Chats.Message;
using BCService.Utils;
using BCCommon.Enums;

namespace BCService.Chats
{
    public class ThreadService : IThreadService
    {
        private readonly IDatabaseContext databaseContext;
        private readonly IApplicationContextService applicationContextService;
        private readonly IThreadData threadData;
        private readonly IThreadParticipantData threadParticipantData;
        private readonly IMessageData messageData;
        private readonly IMessageReceiverData messageReceiverData;
        private readonly IUserResolverService userResolverService;
        private readonly IMessageService messageService;

        public ThreadService(IDatabaseContext databaseContext,
            IApplicationContextService applicationContextService,
            IThreadData threadData,
            IThreadParticipantData threadParticipantData,
            IMessageData messageData,
            IMessageReceiverData messageReceiverData,
            IUserResolverService userResolverService,
            IMessageService messageService)
        {
            this.databaseContext = databaseContext;
            this.applicationContextService = applicationContextService;
            this.threadData = threadData;
            this.threadParticipantData = threadParticipantData;
            this.messageData = messageData;
            this.messageReceiverData = messageReceiverData;
            this.userResolverService = userResolverService;
            this.messageService = messageService;
        }

        public IEnumerable<ThreadDto> GetThreads()
        {
            var threads = threadData.GetThreadsAsync(applicationContextService.GetChatUserId()).Result;
            var participantCollection = threadParticipantData.GetThreadParticipantsByThreadIdAsync(threads.Select(p => p.ThreadId)).Result
                .GroupBy(p => p.ThreadId)
                .ToDictionary(p => p.Key, p => p.AsEnumerable());
            var unreadCountCollection = messageData.GetUnreadMessageCountAsync(applicationContextService.GetChatUserId(), threads.Select(p => p.ThreadId)).Result;

            return threads.Select(thread =>
            {
                if (!participantCollection.TryGetValue(thread.ThreadId, out var participants))
                {
                    participants = new ThreadParticipantEntity[0];
                }

                var self = participants.FirstOrDefault(p => p.ParticipantUserId == applicationContextService.GetChatUserId());
                unreadCountCollection.TryGetValue(thread.ThreadId, out var unreadCount);

                return this.ConvertToThreadDto(thread, participants, self, unreadCount);
            });
        }

        public ThreadDto GetThread(long threadId)
        {
            var thread = threadData.GetThreadAsync(threadId).Result;

            if (thread == null)
            {
                throw new ObjectNotFoundException();
            }

            var (participants, self) = this.GetParticipants(thread.ThreadId);

            if (self == null)
            {
                throw new ObjectNotFoundException();
            }

            return this.ConvertToThreadDto(thread, participants, self);
        }

        public ThreadDto Create(ThreadRequestDto requestDto, out bool threadIsNew)
        {
            var participantUserIds = this.ValidateParticipants(ref requestDto);

            ThreadEntity thread = null;
            threadIsNew = true;

            if (requestDto.Type == ThreadType.Private)
            {
                if (participantUserIds?.Count() != 2)
                {
                    throw new ArgumentException($"{requestDto.Type.GetDisplayString()} 必须是一对一。");
                }

                thread = threadData.GetPrivateThreadAsync(participantUserIds[0], participantUserIds[1]).Result;

                if (thread == null)
                {
                    thread = this.CreateThread(requestDto, participantUserIds);
                }
                else
                {
                    threadIsNew = false;
                }
            }

            if (requestDto.Type == ThreadType.Group)
            {
                if (!(participantUserIds?.Count() >= 3))
                {
                    throw new ArgumentException($"{requestDto.Type.GetDisplayString()} 参与人至少为 3 人。");
                }

                thread = this.CreateThread(requestDto, participantUserIds);

                messageService.Create(new MessageDto
                {
                    ThreadId = thread.ThreadId,
                    MessageType = MessageType.Info,
                    Content = $"你 已经加入了 {requestDto.Type.GetDisplayString()}。"
                });
            }

            if (requestDto.Type == ThreadType.CustomerService)
            {
                if (requestDto.BusinessType == null)
                {
                    throw new ArgumentException($"{requestDto.Type.GetDisplayString()} 的业务类型参数不能为空。");
                }

                if (applicationContextService.GetChatUserId().StartsWith($"{RoleType.Admin.ToString().ToUpperInvariant()}-"))
                {
                    if (participantUserIds.Count != 2)
                    {
                        throw new ArgumentException($"由客服启动的 {requestDto.Type.GetDisplayString()} 必须指定参与人。");
                    }

                    thread = this.threadData.GetCustomerServiceThreadAsync(requestDto.BusinessType, requestDto.ReferenceNumber, participantUserIds[0], participantUserIds[1]).Result;
                }
                else
                {
                    thread = this.threadData.GetCustomerServiceThreadAsync(requestDto.BusinessType, requestDto.ReferenceNumber, participantUserIds[0]).Result;
                }

                if (thread == null)
                {
                    var service = requestDto.GetService(applicationContextService.RequestServices);

                    if (service == null)
                    {
                        throw new ArgumentException("不支持指定的业务。");
                    }

                    var chatAdminId = service.Distribute(requestDto, out var messageDto);

                    if (chatAdminId == null)
                    {
                        throw new ArgumentException("无法匹配的客服。");
                    }

                    if (!participantUserIds.Contains(chatAdminId))
                    {
                        participantUserIds.Add(chatAdminId);
                    }

                    thread = this.CreateThread(requestDto, participantUserIds);

                    if (messageDto != null)
                    {
                        messageDto.ThreadId = thread.ThreadId;

                        messageService.Create(messageDto);
                    }
                }
                else
                {
                    threadIsNew = false;
                }
            }

            if (thread != null)
            {
                return this.GetThread(thread.ThreadId);
            }

            throw new NotImplementedException("不支持请求的会话类型。");
        }

        public ThreadDto Update(long threadId, ThreadUpdateRequestDto requestDto)
        {
            var thread = threadData.GetThreadAsync(threadId).Result;

            if (thread == null)
            {
                throw new ObjectNotFoundException();
            }

            var (participants, self) = this.GetParticipants(thread.ThreadId);

            if (self == null)
            {
                throw new ObjectNotFoundException();
            }

            if (thread.Status != ThreadStatus.Chating.ToString())
            {
                throw new ArgumentException($"不能修改会话信息，因为当前会话 {thread.Status.AsEnum<ThreadStatus>().GetDisplayString()}。");
            }

            if (thread.Type == ThreadType.Private.ToString())
            {
                // TODO: ...
            }

            if (thread.Type == ThreadType.Group.ToString())
            {
                // TODO: ...
            }

            if (thread.Type == ThreadType.CustomerService.ToString())
            {
                // TODO: ...
            }

            throw new NotImplementedException("不支持请求的会话类型。");
        }

        public ThreadDto Close(long threadId, ThreadCloseRequestDto requestDto)
        {
            var thread = threadData.GetThreadAsync(threadId).Result;

            if (thread == null)
            {
                throw new ObjectNotFoundException();
            }

            if (thread.Type == ThreadType.Private.ToString())
            {
                throw new ArgumentException($"{ThreadType.Private.GetDisplayString()} 不支持此操作。");
            }

            var (participants, self) = this.GetParticipants(thread.ThreadId);

            if (self == null)
            {
                throw new ObjectNotFoundException();
            }

            if (thread.Type == ThreadType.Group.ToString())
            {
                if (thread.CreateUserId != applicationContextService.GetChatUserId())
                {
                    throw new ArgumentException($"只有群管理员才能关闭 {ThreadType.Group.GetDisplayString()}。");
                }

                this.messageService.Create(new MessageRequestDto
                {
                    ThreadId = thread.ThreadId,
                    MessageType = MessageType.Info,
                    Content = "群会话已关闭。"
                });

                thread.Status = ThreadStatus.Closed.ToString();
                thread.EditTime = DateTime.Now;
                thread.EditUserId = applicationContextService.GetChatUserId();

                thread = threadData.UpdateAsync(thread).Result;

                return this.ConvertToThreadDto(thread, participants, self);
            }

            if (thread.Type == ThreadType.CustomerService.ToString())
            {
                // TODO: 是否需要限制只有客服才能关闭会话?

                thread.Status = ThreadStatus.Closed.ToString();
                thread.EditTime = DateTime.Now;
                thread.EditUserId = applicationContextService.GetChatUserId();

                thread = threadData.UpdateAsync(thread).Result;

                var service = thread.GetService(applicationContextService.RequestServices);

                if (service == null)
                {
                    throw new ArgumentException("不支持指定的业务。");
                }

                var threadDto = this.ConvertToThreadDto(thread, participants, self);

                service.Close(threadDto, requestDto.Reason);

                return threadDto;
            }

            throw new NotImplementedException("不支持请求的会话类型。");
        }

        public void Delete(long threadId)
        {
            throw new NotImplementedException();
        }

        public ThreadDto MarkAsRead(long threadId, MarkAsReadRequestDto requestDto)
        {
            if (requestDto.MessageId == null)
            {
                messageReceiverData.UpdateAllReadTimeAsync(applicationContextService.GetChatUserId(), threadId);
            }
            else
            {
                if (requestDto.MarkPreviousAsRead == true)
                {
                    messageReceiverData.UpdateAllReadTimeAsync(applicationContextService.GetChatUserId(), threadId, requestDto.MessageId.Value);
                }
                else
                {
                    messageReceiverData.UpdateReadTimeAsync(applicationContextService.GetChatUserId(), threadId, requestDto.MessageId.Value);
                }
            }

            return this.GetThread(threadId);
        }

        public ThreadDto AttachParticipants(long threadId, AttachParticipantsRequestDto requestDto)
        {
            if (requestDto.UserIds.Any(p => p == applicationContextService.GetChatUserId()))
            {
                throw new ArgumentException("要加入到群会话，必须由群成员进行邀请。");
            }

            var thread = threadData.GetThreadAsync(threadId).Result;

            if (thread == null)
            {
                throw new ObjectNotFoundException();
            }

            var (participants, self) = this.GetParticipants(thread.ThreadId);

            if (self == null)
            {
                throw new ObjectNotFoundException();
            }

            if (thread.Status != ThreadStatus.Chating.ToString())
            {
                throw new ArgumentException($"不能修改会话信息，因为当前会话 {thread.Status.AsEnum<ThreadStatus>().GetDisplayString()}。");
            }

            if (thread.Type == ThreadType.Private.ToString())
            {
                List<string> list = new List<string>(participants.Select(p => p.ParticipantUserId));

                foreach (var userId in requestDto.UserIds)
                {
                    if (!list.Contains(userId))
                    {
                        list.Add(userId);
                    }
                }

                return this.Create(new ThreadRequestDto().MergeFrom(thread, onAfter: (dest, _) =>
                {
                    dest.Type = ThreadType.Group;
                    dest.ParticipantUserIds = list;
                }), out var _);
            }
            else
            {
                List<ThreadParticipantEntity> newParticipants = new List<ThreadParticipantEntity>();

                foreach (var userId in requestDto.UserIds)
                {
                    if (userResolverService.Resolve(userId).RoleType == null)
                    {
                        throw new ArgumentException("选择的用户不存在。");
                    }

                    if (!participants.Any(p => p.ParticipantUserId == userId))
                    {
                        newParticipants.Add(new ThreadParticipantEntity
                        {
                            ThreadId = threadId,
                            ParticipantUserId = userId,
                            Muted = false,
                            InviteUserId = applicationContextService.GetChatUserId(),
                            JoinTime = DateTime.Now,
                            Status = ThreadParticipantStatus.Joined.ToString(),
                            CreateTime = DateTime.Now,
                            CreateUserId = applicationContextService.GetChatUserId(),
                            EditTime = DateTime.Now,
                            EditUserId = applicationContextService.GetChatUserId()
                        });
                    }
                }

                foreach (var item in newParticipants)
                {
                    this.messageService.Create(new MessageRequestDto
                    {
                        ThreadId = thread.ThreadId,
                        MessageType = MessageType.Info,
                        Content = $"{{{{USER:{applicationContextService.GetChatUserId()}}}}} 邀请 {{{{USER:{item.ParticipantUserId}}}}} 加入了 {thread.Type.AsEnum<ThreadType>().GetDisplayString()}。"
                    });
                }

                if (newParticipants.Count > 0)
                {
                    this.threadParticipantData.InsertAllAsync(newParticipants);
                }

                return this.ConvertToThreadDto(thread);
            }
        }

        public ThreadDto DetachParticipants(long threadId, DetachParticipantsRequestDto requestDto)
        {
            var thread = threadData.GetThreadAsync(threadId).Result;

            if (thread == null)
            {
                throw new ObjectNotFoundException();
            }

            var (participants, self) = this.GetParticipants(thread.ThreadId);

            if (self == null)
            {
                throw new ObjectNotFoundException();
            }

            if (thread.Status != ThreadStatus.Chating.ToString())
            {
                throw new ArgumentException($"不能修改会话信息，因为当前会话 {thread.Status.AsEnum<ThreadStatus>().GetDisplayString()}。");
            }

            List<ThreadParticipantEntity> removedParticipants = new List<ThreadParticipantEntity>();

            foreach (var userId in requestDto.UserIds)
            {
                if (applicationContextService.GetChatUserId() != thread.CreateUserId)
                {
                    if (userId != applicationContextService.GetChatUserId())
                    {
                        throw new ArgumentException("只有管理员才能将用户从群会话中移除。");
                    }
                }
                else
                {
                    if (userId == thread.CreateUserId)
                    {
                        throw new ArgumentException("管理员不能从群会话中退出。");
                    }
                }

                var participant = participants.FirstOrDefault(p => p.ParticipantUserId == userId);

                if (participant != null)
                {
                    participant.RemoveUserId = applicationContextService.GetChatUserId();
                    participant.ExitTime = DateTime.Now;
                    participant.Status = ThreadParticipantStatus.Exited.ToString();
                    participant.EditTime = DateTime.Now;
                    participant.EditUserId = applicationContextService.GetChatUserId();

                    removedParticipants.Add(participant);
                }
            }

            if (removedParticipants.Count > 0)
            {
                this.threadParticipantData.UpdateAllAsync(removedParticipants).Wait();
            }

            return this.ConvertToThreadDto(thread);
        }

        private List<string> ValidateParticipants(ref ThreadRequestDto requestDto)
        {
            var participantUserIds = new List<string>
            {
                applicationContextService.GetChatUserId()
            };

            foreach (var participantUserId in requestDto.ParticipantUserIds)
            {
                if (participantUserId == applicationContextService.GetChatUserId())
                {
                    continue;
                }

                if (userResolverService.Resolve(participantUserId).RoleType == null)
                {
                    throw new ArgumentException("选择的用户不存在。");
                }

                participantUserIds.Add(participantUserId);
            }

            return participantUserIds;
        }

        private ThreadEntity CreateThread(ThreadRequestDto requestDto, List<string> participantUserIds)
        {
            databaseContext.BeginTransaction();

            var thread = threadData.InsertAsync(new ThreadEntity
            {
                Title = requestDto.Title,
                Subtitle = requestDto.Subtitle,
                Type = requestDto.Type.ToString(),
                BusinessType = requestDto.BusinessType,
                ReferenceNumber = requestDto.ReferenceNumber,
                Status = ThreadStatus.Chating.ToString(),
                CreateTime = DateTime.Now,
                CreateUserId = applicationContextService.GetChatUserId(),
                EditTime = DateTime.Now,
                EditUserId = applicationContextService.GetChatUserId()
            }).Result;
            threadParticipantData.InsertAllAsync(participantUserIds.Select(p => new ThreadParticipantEntity
            {
                ThreadId = thread.ThreadId,
                ParticipantUserId = p,
                InviteUserId = applicationContextService.GetChatUserId(),
                JoinTime = DateTime.Now,
                Status = ThreadParticipantStatus.Joined.ToString(),
                CreateTime = DateTime.Now,
                CreateUserId = applicationContextService.GetChatUserId(),
                EditTime = DateTime.Now,
                EditUserId = applicationContextService.GetChatUserId()
            }));

            databaseContext.Commit();

            return thread;
        }

        private (IEnumerable<ThreadParticipantEntity> participants, ThreadParticipantEntity self) GetParticipants(long threadId)
        {
            var participants = threadParticipantData.GetThreadParticipantsByThreadIdAsync(new[] { threadId }).Result;
            var self = participants.FirstOrDefault(p => p.ParticipantUserId == applicationContextService.GetChatUserId());

            return (participants, self);
        }

        private ThreadDto ConvertToThreadDto(ThreadEntity entity, IEnumerable<ThreadParticipantEntity> participants = null, ThreadParticipantEntity self = null, int? unreadCount = null)
        {
            if (participants == null)
            {
                (participants, self) = this.GetParticipants(entity.ThreadId);
            }

            var threadDto = new ThreadDto
            {
                ThreadId = entity.ThreadId,
                Title = entity.Title,
                Subtitle = entity.Subtitle,
                Announcement = entity.Announcement,
                Type = entity.Type.AsEnum<ThreadType>(),
                BusinessType = entity.BusinessType,
                ReferenceNumber = entity.ReferenceNumber,
                Participants = participants.Select(p => userResolverService.Resolve(p.ParticipantUserId).AsParticipantDto(p))
            };

            if (self != null)
            {
                threadDto.Muted = self.Muted;
                threadDto.UserName = userResolverService.Resolve(self.ParticipantUserId).FullName;
                threadDto.AliasName = self.AliasName;
            }

            if (unreadCount == null)
            {
                var unreadCounts = messageData.GetUnreadMessageCountAsync(applicationContextService.GetChatUserId(), new[] { entity.ThreadId }).Result;

                if (unreadCounts.TryGetValue(entity.ThreadId, out var value))
                {
                    threadDto.UnreadCount = value;
                }
            }
            else
            {
                threadDto.UnreadCount = unreadCount.Value;
            }

            if (entity.LastChatTime != null)
            {
                threadDto.LastChat = new ThreadLastChatDto
                {
                    Content = entity.LastChatContent,
                    ChatTime = entity.LastChatTime.Value,
                    User = userResolverService.Resolve(entity.LastChatUserId).AsParticipantDto(participants.FirstOrDefault(p => p.ParticipantUserId == entity.LastChatUserId))
                };
            }

            return threadDto;
        }
    }
}
